/*
 * Copyright 2008 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.splink.library.tween {
	import org.splink.library.distributor.Distributor;
	import org.splink.library.distributor.Dtype;
	import org.splink.library.distributor.IDistributor;
	import org.splink.library.queue.QEvent;
	import org.splink.library.tween.sprop.ISpecialProp;

	import flash.display.DisplayObject;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.utils.getTimer;

	/**
	 * The default <code>IQTween</code> implementation
	 * 
	 * @author Max Kugland
	 */
	public class QTween extends Distributor implements IQTween {
		private static const _provider : DisplayObject = new Shape();		private static const ID : String = "QTween";
		private static const DEFAULT_EASING : Function = function (t : Number, b : Number, c : Number, d : Number):Number {
			return c * t / d + b;
		};		

		private var _target : Object;
		private var _ease : Function = DEFAULT_EASING;
		private var _delay : int = 0;
		private var _duration : int = 500;
		private var _id : String;
		private var _startTime : int;
		private var _stopTime : int = 0;
		private var _prop : Prop;
		private var _head : Prop;
		private var _progressEvent : QEvent;
		private var _startEvent : QEvent;

		public function QTween(target : Object, id : String = ID) {
			_target = target;
			_id = id;
		}

		/**
		 * @inheritDoc
		 */
		public function add(prop : String, to : Number, from : Number = NaN, sprop : ISpecialProp = null) : IQTween {
			var prev : Prop = _prop;
			_prop = new Prop(prop, from, to, sprop);
			if(_head == null) _head = _prop;
			if(prev) prev.next = _prop;
			return this;
		}

		/**
		 * @inheritDoc
		 */
		public function duration(duration : uint) : IQTween {
			_duration = duration;
			return this;
		}

		/**
		 * @inheritDoc
		 */
		public function delay(delay : uint) : IQTween {
			_delay = delay;
			return this;
		}

		/**
		 * @inheritDoc
		 */
		public function easing(ease : Function) : IQTween {
			_ease = ease;
			return this;
		}

		/**
		 * @inheritDoc
		 */
		public function start() : void {
			if(_startEvent) {
				distribute(_startEvent);
			}
			
			if(_head == null) {
				distribute(new QEvent(this, QEvent.COMPLETE, _id));
				return;
			}
			
			_startTime = getTimer();
			if(_stopTime != 0) {
				_startTime = getTimer() - _stopTime;
				_stopTime = 0;
			} else {
				rewind();
			}
			
			_provider.addEventListener(Event.ENTER_FRAME, run);
		}

		private function run(e : Event) : void {
			var now : int = getTimer();
			var time : int = now - _startTime;
			if(time < _delay) {
				return;
			} 
			if(_delay > 0) {
				_startTime = now;
				time = now - _startTime; 
				_delay = time = 0;
			}
			
			const running : Boolean = time < _duration;
			const percent : Number = running ? Number(_ease(time, 0, 1, _duration)) : 1;
			
			var p : Prop = _head;
			while(true) {
				if(p.sprop) {
					p.sprop.apply(_target, p.name, p.from, p.to, (p.from + percent * p.change), percent);					
				} else {
					_target[p.name] = p.from + percent * p.change;
				}
				
				if(p.next) {
					p = p.next;
				} else {
					break;
				}
			}
			
			if(_progressEvent) {
				_progressEvent.progress.current = Math.min(time, _duration);				_progressEvent.progress.total = _duration;
				distribute(_progressEvent);
			} 
			
			if(!running) {
				distribute(new QEvent(this, QEvent.COMPLETE, _id));
				finalize();
			} 
		}

		/**
		 * @inheritDoc
		 */
		public function stop() : void {
			_provider.removeEventListener(Event.ENTER_FRAME, run);
			_stopTime = getTimer() - _startTime;
		}

		/**
		 * @inheritDoc
		 */
		public function reset() : void {
			stop();
			_stopTime = 0;
			rewind();
		}

		private function rewind() : void {
			var p : Prop = _head;
			while(true) {
				if(p.sprop) {
					p.from = isNaN(p.from) ? p.sprop.getValue(p.name) : p.from;
					p.sprop.apply(_target, p.name, p.from, p.to, p.from, 0);
				} else {
					_target[p.name] = p.from = isNaN(p.from) ? _target[p.name] : p.from;
				}
				
				if(p.next) {
					p = p.next;
				} else {
					break;
				}
			}
		}

		/**
		 * @inheritDoc
		 */
		public function get id() : String {
			return _id;
		}

		/**
		 * @inheritDoc
		 */
		override public function finalize() : void {
			_provider.removeEventListener(Event.ENTER_FRAME, run);
			super.finalize();
		}

		/**
		 * @inheritDoc
		 */
		override public function register(type : Dtype, fnc : Function, params : Object = null, priority : uint = uint.MIN_VALUE) : IDistributor {
			if(type == QEvent.PROGRESS) {
				_progressEvent = new QEvent(this, QEvent.PROGRESS, _id);
			} else if(type == QEvent.START) {
				_startEvent = new QEvent(this, QEvent.START, _id);
			}
			return super.register(type, fnc, params, priority);
		}
	}
}

import org.splink.library.tween.sprop.ISpecialProp;

internal class Prop {
	public var next : Prop;
	public var name : String;
	private var _from : Number;
	public function get from() : Number {
		return _from;
	}
	public function set from(f:Number) : void {
		_from = f;
		this.change = to - _from;
	}
	public var to : Number;
	public var change : Number;
	public var sprop : ISpecialProp;

	public function Prop(name : String, from : Number, to : Number, sprop : ISpecialProp = null) {
		this.name = name;
		_from = from;
		this.to = to;
		this.change = to - from;
		this.sprop = sprop;
	}
}